//
// Created by QU on 24-8-20.
//
#include <boost/asio.hpp>

#include <memory>
#include <cstring> // for memcpy
#include <iostream>
#include <queue>

namespace asio = boost::asio;
namespace ip = boost::asio::ip;

/*
class Session
{
public:
    explicit Session(std::shared_ptr<ip::tcp::socket> socket_p) : socket_(std::move(socket_p)) { }
    void Connect(const ip::tcp::endpoint& endpoint) { socket_->connect(endpoint); }
private:
    std::shared_ptr<ip::tcp::socket> socket_;
};
*/

const size_t RECV_SIZE = 1024;

class MsgNode
{
public:
    // 作为发送节点的构造函数
    MsgNode(const char* msg, size_t total_len)
        : m_total_len(total_len), m_cur_len(0), m_msg_p(std::make_unique<char[]>(total_len))
    {
        memcpy(m_msg_p.get(), msg, total_len);
    }

    // 作为接受节点的构造函数
    MsgNode(size_t total_len)
        : m_total_len(total_len), m_cur_len(0), m_msg_p(std::make_unique<char[]>(total_len))
    {
        std::memset(m_msg_p.get(), 0, total_len);
    }

    size_t m_total_len;
    size_t m_cur_len;
    std::unique_ptr<char[]> m_msg_p;
};

class Session
{
public:
    Session(std::shared_ptr<asio::ip::tcp::socket> socket) : m_socket(socket)
    {
    }

    void Connect(const asio::ip::tcp::endpoint& ep)
    {
        m_socket->connect(ep);
    }

    // 这里使用shared_ptr是因为引用计数+1, 保证函数传参时候MsgNode对象的存活.
    void WriteCallBackErr(const boost::system::error_code& ec, std::size_t bytes_transferred,
                          std::shared_ptr<MsgNode> msg_node_p)
    {
        std::cout << "Handler: bytes_transferred: " << bytes_transferred << std::endl;
        // 中间有可能很多次调用这个回调函数.
        if (bytes_transferred + msg_node_p->m_cur_len < msg_node_p->m_total_len)
        {
            msg_node_p->m_cur_len += bytes_transferred;
            m_socket->async_write_some(
                // 第一个参数是buffer指针, 第二个参数是需要构造buffer的长度.
                asio::buffer(msg_node_p->m_msg_p.get() + msg_node_p->m_cur_len,
                             msg_node_p->m_total_len - msg_node_p->m_cur_len),
                std::bind(WriteCallBackErr, this, std::placeholders::_1, std::placeholders::_2, msg_node_p)
            );
        }
    }

    void WriteToSocketErr(const std::string& buf)
    {
        m_send_node = std::make_shared<MsgNode>(buf.data(), buf.size());
        // 你这里当然可以使用c++11的std::bind
        m_socket->async_write_some(asio::buffer(m_send_node->m_msg_p.get(), m_send_node->m_total_len),
                                   std::bind(&Session::WriteCallBackErr, this,
                                             std::placeholders::_1,
                                             std::placeholders::_2,
                                             m_send_node)
        );
        // 如果可以使用c++14, lambda表达式比std::bind更加值得推荐.

        // 第一个参数 , ConstBufferSequence 在02提到过, 还有一个MutableBufferSequence,
        //          这些都可以通过boost::buffer构造出来(取决于你传给buffer()的参数是不是const的).

        // 第二个参数, WriteToken, 是一个写回调函数(handler).
        //          可以在模版参数看到, 这个handler是一个签名是 void (boost::system::error_code, std::size_t) 的函数.
        //
        //

        /*
          template <typename ConstBufferSequence,
                            BOOST_ASIO_COMPLETION_TOKEN_FOR(void (boost::system::error_code,
                            std::size_t)) WriteToken = default_completion_token_t<executor_type>
                    >
            auto async_write_some(const ConstBufferSequence& buffers,
                            WriteToken&& token = default_completion_token_t<executor_type>())
                -> decltype(
                    async_initiate<WriteToken, void (boost::system::error_code, std::size_t)>( declval<initiate_async_send>(), token,
                        buffers, socket_base::message_flags(0)))
            {
                return async_initiate<WriteToken,
                                      void (boost::system::error_code, std::size_t)>(
                    initiate_async_send(this), token,
                    buffers, socket_base::message_flags(0));
            }
        */
    }

    // 这里没有考虑到多线程的情况.
    /*
     * 而实际开发的场景用户是不清楚底层tcp的多路复用调用情况的，用户想发送数据的时候就调用WriteToSocketErr,
     * 或者循环调用WriteToSocketErr，很可能在一次没发送完数据还未调用回调函数时再次调用WriteToSocketErr，
     * 因为boost::asio封装的时epoll和iocp等多路复用模型，当写事件就绪后就发数据，发送的数据按照async_write_some调用的顺序发送，
     * 所以回调函数内调用的async_write_some可能并没有被及时调用。
     *
     * //用户发送数据
     * WriteToSocketErr("Hello World!");
     * //用户无感知下层调用情况又一次发送了数据
     * WriteToSocketErr("Hello World!");
     *
     *   那么很可能第一次只发送了Hello，后面的数据没发完，第二次发送了Hello World!之后又发送了World!
     *   所以对端收到的数据很可能是”HelloHello World! World!”
     *   那怎么解决这个问题呢，我们可以通过队列保证应用层的发送顺序。我们在Session中定义一个发送队列，然后重新定义正确的异步发送函数和回调处理
     */

    // 后面的因为使用到了队列, 在队列里存储了MsgNode, 所以这两个函数不需要std::shared_ptr<MsgNode> msg_node_p,
    // 将需要发送的东西原位构造到队列里得到就可以

    // 使用队列就是为了保证发送包的时序性
    // 使用队列就是为了保证发送包的时序性
    void WriteToSocket(const std::string& buf)
    {
        m_send_node = std::make_shared<MsgNode>(buf.data(), buf.size());
        m_send_queue.emplace(m_send_node);

        if (m_send_queue.size() > 1) // 队列中已有未发送完的消息，等待回调处理
        {
            return;
        }

        // 启动异步写操作
        StartAsyncWrite();
    }

    void WriteCallBack(const boost::system::error_code& ec, std::size_t bytes_transferred)
    {
        if (ec)
        {
            std::cerr << "Error: " << ec.value() << ". what(): " << ec.message() << std::endl;
            return;
        }

        auto& queue_first_node_p = m_send_queue.front();
        queue_first_node_p->m_cur_len += bytes_transferred;

        if (queue_first_node_p->m_cur_len < queue_first_node_p->m_total_len)
        {
            // 继续发送未完成的部分
            StartAsyncWrite();
            return;
        }

        m_send_queue.pop(); // 移除已发送完的消息

        if (!m_send_queue.empty()) // 如果队列里有新消息，继续发送
        {
            StartAsyncWrite();
        }
    }

    void StartAsyncWrite()
    {
        auto& queue_first_node_p = m_send_queue.front();
        m_socket->async_write_some(
            asio::buffer(queue_first_node_p->m_msg_p.get() + queue_first_node_p->m_cur_len,
                         queue_first_node_p->m_total_len - queue_first_node_p->m_cur_len),
            std::bind(&Session::WriteCallBack, this, std::placeholders::_1, std::placeholders::_2)
        );
    }

    /*
    async_write_some函数不能保证每次回调函数触发时发送的长度为要总长度，这样我们每次都要在回调函数判断发送数据是否完成，
    asio提供了一个更简单的发送函数async_send，这个函数在发送的长度未达到我们要求的长度时就不会触发回调，
    所以触发回调函数时要么时发送出错了要么是发送完成了,其内部的实现原理就是帮我们不断的调用async_write_some直到完成发送，
    所以async_send不能和async_write_some混合使用，我们基于async_send封装另外一个发送函数

    生产环境中, 其实个更加推荐使用async_send, 因为他比async_write_some简单.
    */

    /*
    //不能与async_write_some混合使用
    void Session::WriteAllToSocket(const std::string& buf) {
        //插入发送队列
        _send_queue.emplace(new MsgNode(buf.c_str(), buf.length()));
        //pending状态说明上一次有未发送完的数据
        if (_send_pending) {
            return;
        }
        //异步发送数据，因为异步所以不会一下发送完
        this->_socket->async_send(asio::buffer(buf),
            std::bind(&Session::WriteAllCallBack, this,
                std::placeholders::_1, std::placeholders::_2));
        _send_pending = true;
    }
    void Session::WriteAllCallBack(const boost::system::error_code& ec, std::size_t bytes_transferred){
        if (ec.value() != 0) {
            std::cout << "Error occured! Error code = "
                << ec.value()
                << ". Message: " << ec.message();
            return;
        }
        //如果发送完，则pop出队首元素
        _send_queue.pop();
        //如果队列为空，则说明所有数据都发送完,将pending设置为false
        if (_send_queue.empty()) {
            _send_pending = false;
        }
        //如果队列不是空，则继续将队首元素发送
        if (!_send_queue.empty()) {
            auto& send_data = _send_queue.front();
            this->_socket->async_send(asio::buffer(send_data->_msg + send_data->_cur_len, send_data->_total_len - send_data->_cur_len),
                std::bind(&Session::WriteAllCallBack,
                    this, std::placeholders::_1, std::placeholders::_2));
        }
    }
    */


    //不考虑粘包情况， 先用固定的字节接收
    void ReadFromSocket()
    {
        if (m_recv_pending)
        {
            return;
        }
        //可以调用构造函数直接构造，但不可用已经构造好的智能指针赋值
        /*auto m_recv_nodez = std::makem_unique<MsgNode>(RECVSIZE);
        m_recv_node = m_recv_nodez;*/

        m_recv_node = std::make_shared<MsgNode>(RECV_SIZE);
        m_socket->async_read_some(asio::buffer(m_recv_node->m_msg_p.get(), m_recv_node->m_total_len),
                                  std::bind(&Session::ReadCallBack, this, std::placeholders::_1,
                                            std::placeholders::_2));
        m_recv_pending = true;
    }

    void Session::ReadCallBack(const boost::system::error_code& ec, std::size_t bytesm_transferred)
    {
        m_recv_node->m_cur_len += bytesm_transferred;
        //没读完继续读
        if (m_recv_node->m_cur_len < m_recv_node->m_total_len)
        {
            m_socket->async_read_some(asio::buffer(m_recv_node->m_msg_p.get() + m_recv_node->m_cur_len,
                                                   m_recv_node->m_total_len - m_recv_node->m_cur_len),
                                      std::bind(&Session::ReadCallBack, this,
                                                std::placeholders::_1, std::placeholders::_2));
            return;
        }
        //将数据投递到队列里交给逻辑线程处理，此处略去
        //如果读完了则将标记置为false
        m_recv_pending = false;
        //指针置空
        m_recv_node = nullptr;
    }


    /*
    void Session::ReadAllFromSocket(const std::string& buf) {
        if (_recv_pending) {
            return;
        }
        //可以调用构造函数直接构造，但不可用已经构造好的智能指针赋值
        /*auto _recv_nodez = std::make_unique<MsgNode>(RECVSIZE);
        _recv_node = _recv_nodez;#1#
        _recv_node = std::make_shared<MsgNode>(RECVSIZE);
        _socket->async_receive(asio::buffer(_recv_node->_msg, _recv_node->_total_len), std::bind(&Session::ReadAllCallBack, this,
            std::placeholders::_1, std::placeholders::_2));
        _recv_pending = true;
    }
    void Session::ReadAllCallBack(const boost::system::error_code& ec, std::size_t bytes_transferred) {
        _recv_node->_cur_len += bytes_transferred;
        //将数据投递到队列里交给逻辑线程处理，此处略去
        //如果读完了则将标记置为false
        _recv_pending = false;
        //指针置空
        _recv_node = nullptr;
    }
    */

private:
    std::queue<std::shared_ptr<MsgNode>> m_send_queue;
    std::shared_ptr<MsgNode> m_send_node;
    std::shared_ptr<asio::ip::tcp::socket> m_socket;
    // 这里本来有一个 bool m_send_pending; , 但是我觉得不需要, 叫我去掉了.

    std::shared_ptr<MsgNode> m_recv_node;
    bool m_recv_pending;
};


int main(int argc, char* argv[])
{
}
